import compose from "./compose";
import { match } from "path-to-regexp";
import {
  ContextType,
  HandleType,
  MiddlewareType,
  RourterOptsType,
  RourterStackType,
  RouterRegOptionType,
} from "../types";

/**
 * 云函数路由
 */
export class Router {
  /**
   * 路由prefix统一前缀
   */
  private _prefix: string = "";

  /**
   * 路由stack表中间件数组
   */
  private _stack: RourterStackType[] = [];

  /**
   * 默认构造函数
   *
   * @param opts 配置选项
   */
  constructor(opts?: RourterOptsType) {
    if (opts && opts.prefix) {
      this._prefix = opts.prefix;
    }
  }

  /**
   * 路由前缀
   *
   * 所有添加到该路由器实例上的路由都会自动添加该前缀
   *
   * @api public
   * @param {String} prefix 字符串前缀
   * @returns this
   * @example
   * ```javascript
   * this.prefix("/api");
   * ```
   */
  public prefix(prefix: string): this {
    this._prefix = prefix;
    return this;
  }

  /**
   * 查找匹配路由
   *
   * @api private
   * @param {String} method 请求方法
   * @param {String} path url路径
   * @returns {RourterStackType|null} 匹配路由对象或空值
   */
  private findMatch(method: string, path: string): RourterStackType | null {
    // 遍历路由表进行检查匹配
    for (const item of this._stack) {
      // 不是指定方法类型直接跳过
      if (item.method !== method.toUpperCase()) {
        continue;
      }
      // 检查是否匹配路径
      const result = match(item.path)(path);
      if (result !== false && typeof result === "object") {
        return {
          ...item,
          params: { ...result.params },
        };
      }
    }
    return null;
  }

  /**
   * 路由表中间件
   *
   * @api public
   * @returns {HandleType}中间件函数
   * @example
   * ```javascript
   * const app = new Application();
   * const router = new Router();
   *
   * app.use(router.routes());
   * ```
   */
  public routes(): HandleType {
    return async (ctx: ContextType, next?: () => Promise<void>) => {
      const { method, path } = ctx;
      // 没有url参数的跳过查找
      if (!path) {
        return Promise.resolve();
      }
      // 查找匹配路由
      const result = this.findMatch(method, path);
      if (result) {
        // 路由参数注入上下文后执行函数
        const { params, requests } = result;
        ctx.params = { ...params };
        return await compose(requests)(ctx);
      }
      // 无匹配路由时提示方法和路径
      ctx.body = {
        message: `Router Not Found ${method} ${path}`,
        code: 404,
      };
      // 执行下一个中间件直至没有next
      return !next ? Promise.resolve() : await next();
    };
  }

  /**
   * 将嵌套的数组打平为一维数组
   *
   * @api private
   * @param {T} arr 中间件函数数组
   * @returns {T[]} 函数数组
   */
  private flatten<T>(arr: T[]): T[] {
    let result: T[] = [];
    for (let item of arr) {
      if (Array.isArray(item)) {
        result.push(...this.flatten(item));
      } else {
        result.push(item);
      }
    }
    return result;
  }

  /**
   * 注册方法路由请求
   *
   * @api public
   * @param {String} path url路径
   * @param {String} method 请求方法
   * @param {HandleType} handle 执行句柄函数
   * @param {RouterRegOptionType} option 注册参数
   * @returns this
   * @example
   * ```javascript
   * this.register("get", "/index", async ctx => {
   *   ctx.body = ctx;
   * });
   * ```
   */
  public register(
    method: string,
    path: string,
    handle: HandleType,
    option?: RouterRegOptionType
  ): this {
    // 请求方法和路径是必须的
    if (!method) {
      throw new Error("Router method is required!");
    }
    if (!path) {
      throw new Error("Router path is required!");
    }
    // 句柄必须是函数
    if (typeof handle !== "function") {
      throw new TypeError("Router handle must be a function!");
    }

    // 解析URL
    const { pathname } = new URL(path, "https://cloud.com/");

    // 初始注册参数
    let _middleware: MiddlewareType[] = [];
    let _ignoreGlobalPrefix = false;
    if (option) {
      const { middleware, ignoreGlobalPrefix } = option;
      if (Array.isArray(middleware)) {
        _middleware = middleware;
      }
      if (typeof ignoreGlobalPrefix === "boolean") {
        _ignoreGlobalPrefix = ignoreGlobalPrefix;
      }
    }

    // 是否忽略路由前缀
    let _path = pathname;
    if (!_ignoreGlobalPrefix) {
      _path = this._prefix + pathname;
    }

    // 中间件先行，最后执行句柄
    const handleArr = [...this.flatten(_middleware), handle];

    // 将路由对象添加到stack数组就行
    this._stack.push({
      method: method.toUpperCase(),
      path: decodeURIComponent(_path),
      requests: handleArr,
    });
    return this;
  }

  /**
   * 创建注册路由方法请求
   *
   * @api private
   * @param {String} method 请求方法
   * @returns {Function} 注册路由方法请求函数
   */
  private createRouterMethod(method: string): Function {
    return (
      path: string,
      handle: HandleType,
      option?: RouterRegOptionType
    ): this => {
      return this.register(method, path, handle, option);
    };
  }

  /**
   * 注册GET路由请求
   *
   * @api public
   * @param {String} path url路径
   * @param {HandleType} handle 执行句柄函数
   * @param {RouterRegOptionType} option 注册参数
   * @example
   * ```javascript
   * this.get("/get", async ctx => { ctx.body = ctx });
   * ```
   */
  public get = this.createRouterMethod("GET");

  /**
   * 注册POST路由请求
   *
   * @api public
   * @param {String} path url路径
   * @param {HandleType} handle 执行句柄函数
   * @param {RouterRegOptionType} option 注册参数
   * @example
   * ```javascript
   * this.post("/post", async ctx => { ctx.body = ctx });
   * ```
   */
  public post = this.createRouterMethod("POST");

  /**
   * 注册PUT路由请求
   *
   * @api public
   * @param {String} path url路径
   * @param {HandleType} handle 执行句柄函数
   * @param {RouterRegOptionType} option 注册参数
   * @example
   * ```javascript
   * this.put("/put", async ctx => { ctx.body = ctx });
   * ```
   */
  public put = this.createRouterMethod("PUT");

  /**
   * 注册DELETE路由请求
   *
   * @api public
   * @param {String} path url路径
   * @param {HandleType} handle 执行句柄函数
   * @param {RouterRegOptionType} option 注册参数
   * @example
   * ```javascript
   * this.delete("/delete", async ctx => { ctx.body = ctx });
   * ```
   */
  public delete = this.createRouterMethod("DELETE");
}
